<template>
  <!-- 三维画布 -->
  <div style="width: 100%; height: 100%; position: relative">
    <div id="three_div" ref="draw" class="draw" />
    <div v-if="loadSuccess" class="loadingBox">
      <div class="progress">
        <div
          class="progress-bar progress-bar-danger progress-bar-striped active"
          :style="{ width: progress }"
        >
          <div class="progress-value">{{ progress }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
  import * as THREE from 'three' // 三维
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' // 控制器
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 控制器
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 控制器
  export default {
    props: {
      faceList: {
        type: Array,
        default: () => []
      },
      capacity: {
        type: [Number, String],
        default: 0
      }
    },
    data() {
      return {
        // 声明渲染器
        renderer: '',
        // 声明相机
        camera: '',
        // 声明场景
        scene: '',
        // 声明几何体
        geometry: '',
        // 声明材质
        material: '',
        // 声明网格
        mesh: '',
        // 声明相机控制器
        controls: '',
        // 画布大小
        clientWidth: '',
        clientHeight: '',
        // 模型组
        faceGroup: [],
        // 记住选中的端口
        selectedPort: null,
        progress: 0,
        loadSuccess: true
      }
    },
    computed: {
      portStatus() {
        return {
          IDLE: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/white.jpg'),
            name: '空闲',
            typeName: 'IDLE'
          },
          PROCESS: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/process.jpg'),
            name: '进行中',
            typeName: 'PROCESS'
          },
          MAIN: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/occupy.jpg'),
            name: '主用',
            typeName: 'MAIN'
          },
          BACKUP: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/backup.jpg'),
            name: '备用',
            typeName: 'BACKUP'
          },
          DAMAGE: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/damage.jpg'),
            name: '损坏',
            typeName: 'DAMAGE'
          },
          OCCUPY: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/damage1.jpg'),
            name: '占用',
            typeName: 'OCCUPY'
          },
          CONN: {
            img: new THREE.TextureLoader().load('/statics/glbstatus/occupy.jpg'),
            name: '连接',
            typeName: 'OCCUPY'
          }
        }
      }
    },
    watch: {
      faceList(val) {
        console.log(val)
      }
    },
    mounted() {
      this.init()
    },
    beforeDestroy() {
      window.removeEventListener('resize', this.changeSize)
      window.removeEventListener('click', this.portClick)
      this.removeObj(this.scene)
      this.renderer && this.renderer.dispose()
      this.renderer.forceContextLoss()
      this.renderer.domElement = null
      this.renderer.content = null
      this.renderer = null
      cancelAnimationFrame(this._animate)
      THREE.Cache.clear()
    },
    methods: {
      init() {
        // 初始化渲染器
        this.initRenderer()
        // 初始化场景
        this.initScene()
        // 初始化相机
        this.initCamera()
        // 引入模型
        this.initgltfLoader()
        // 初始化光源
        this.initLight()
        // 初始化动画
        this.animate()
        // 添加事件
        this.addmeth()
      },
      // 初始化渲染器
      initRenderer() {
        // 实例化渲染器
        this.renderer = new THREE.WebGLRenderer({
          antialias: true, // 是否开启抗锯齿
          alpha: true // 是否可以将背景色设置为透明
        })
        // 设置渲染区域尺寸
        this.renderer.setSize(
          this.$refs.draw.offsetWidth,
          this.$refs.draw.offsetHeight
        )
        // 告诉渲染器需要阴影效果
        this.renderer.shadowMap.enabled = true
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
        // 设置背景色
        this.renderer.setClearColor(0x000000, 0) // 设置背景颜色
        this.$refs.draw.appendChild(this.renderer.domElement)
      },
      // 初始化场景
      initScene() {
        // 实例化场景
        this.scene = new THREE.Scene()
        // 红线是X轴，绿线是Y轴，蓝线是Z轴
        var axesHelper = new THREE.AxesHelper(0)
        this.scene.add(axesHelper)
      },
      // 初始化相机
      initCamera() {
        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        // 参数：PerspectiveCamera
        // fov — 垂直视野角度(从底部到顶部，以度为单位。 默认值为50。)
        // aspect — 长宽比（一般为渲染器、画布长宽比,默认为1）
        // near — 近距离(默认值为0.1)
        // far — 远距离(默认为2000,必须大于近距离的值。)
        this.camera = new THREE.PerspectiveCamera(45, k, 1, 500)
        this.camera.position.set(30, 30, 100)
        // 创建相机控制器
        this.controls = new OrbitControls(this.camera, this.renderer.domElement)
      },
      // 引入外部模型 gltf
      initgltfLoader() {
        const gltfLoader = new GLTFLoader()
        const dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath('/statics/draco/gltf/') // 这个是加载draco算法，这样才能解析压缩后的gltf模型格式.
        gltfLoader.setDRACOLoader(dracoLoader)
        // 引入默认纹理
        gltfLoader.load(
          '/statics/glb/AFS' + this.capacity + '.glb',
          (gltf) => {
            const model = gltf.scene
            console.log(model)
            model.scale.set(0.7, 0.7, 0.7) // 缩放
            model.position.set(0, -20, 0)
            const portGroup =
              model.children &&
              model.children[0].children.find(
                (item) => item.name === 'PORT_GROUP'
              )
            if (!portGroup) return
            const portList = portGroup.children
            portList.forEach((item) => {
              item.nameIndex = item.name.replace(/[^\d]/g, '')
            })
            portList.sort((a, b) => {
              return a.nameIndex - b.nameIndex
            })

            this.faceGroup[0] = portList.filter((item) =>
              item.name.includes('Port_A')
            )
            this.faceGroup[1] = portList.filter((item) =>
              item.name.includes('Port_B')
            )

            this.faceList.forEach((item, index) => {
              item.portList.forEach((port, k) => {
                this.faceGroup[index][k].portData = port
                this.faceGroup[index][k].portId = port.portInfo.portId
              })
            })
            setTimeout(() => {
              // 给端口重新赋纹理
              this.faceGroup.forEach((item) => {
                item.forEach((port) => {
                  const texture = this.portStatus[port.portData.portInfo.status]
                  port.children.forEach((mesh) => {
                    mesh.material = new THREE.MeshPhongMaterial({
                      color: 0xffffff
                    })
                    mesh.material.map = texture.img
                    mesh.textureName = texture.typeName
                  })
                })
              })
            }, 0)
            this.scene.add(model)
          },
          (xhr) => {
            const percentage = Math.floor((xhr.loaded / xhr.total) * 100)
            this.progress = percentage + '%'
            if (percentage >= 99) {
              setTimeout(() => {
                this.loadSuccess = false
              }, 2000)
            }
          }
        )
      },
      // 添加光源
      initLight() {
        // 全局环境光
        const ambientLight = new THREE.AmbientLight('#ffffff', 0.1)
        this.scene.add(ambientLight)
        // 点光源
        const pointLight = new THREE.PointLight('#ffffff', 1)
        pointLight.position.set(100, 500, 500)
        this.camera.add(pointLight)

        this.scene.add(this.camera)
      },
      addmeth() {
        // 监听窗口尺寸变化
        window.addEventListener('resize', this.changeSize, false)
        window.addEventListener('click', this.portClick, false)
      },
      // 运行动画
      animate() {
        this._animate = requestAnimationFrame(this.animate.bind(this)) // 循环调用函数
        // 刷新相机控制器
        this.controls.update()
        this.renderer.render(this.scene, this.camera)
      },
      // 点击端口
      portClick(event) {
        // 保持原事件
        event.preventDefault()
        this.getIntersects(event.layerX, event.layerY)
      },
      getIntersects(layerX, layerY) {
        // 建立射线
        const raycaster = new THREE.Raycaster()
        // 建立一个空物体
        const mouseVector = new THREE.Vector3()
        const x = (layerX / this.clientWidth) * 2 - 1
        const y = -(layerY / this.clientHeight) * 2 + 1
        mouseVector.set(x, y, 1)
        raycaster.setFromCamera(mouseVector, this.camera)
        raycaster.params.Line.threshold = 0.01
        let intersections = []
        intersections = raycaster.intersectObjects(this.scene.children, true)
        let selectedObject = null // 被选中的模型

        if (intersections.length > 0) {
          for (var i = 0; i < intersections.length; i++) {
            // 遍历线相交模型
            if (intersections[i].object instanceof THREE.Mesh) {
              // 取第一个（距离最近）的相交Mesh类型模型
              // 如果要排除地面等参照模型，也可在此处添加判断条件
              selectedObject = intersections[i].object
              break
            }
          }
        }
        if (selectedObject) {
          const selected = new THREE.TextureLoader().load(
            '/statics/glbstatus/selected.jpg'
          )
          if (selectedObject.parent.name.includes('Port_')) {
            if (
              this.selectedPort &&
              this.selectedPort.parent.portData.connPortId !==
              selectedObject.parent.portData.connPortId
            ) {
              this.faceGroup.forEach((item) => {
                item.forEach((port) => {
                  const texture = this.portStatus[port.portData.portInfo.status]
                  if (
                    this.selectedPort.parent.portData.connPortId ===
                    port.portData.connPortId ||
                    this.selectedPort.parent.portData.connPortId ===
                    port.portData.portInfo.portId
                  ) {
                    port.children.forEach((mesh) => {
                      mesh.material.map = texture.img
                      mesh.textureName = texture.typeName
                    })
                  }
                })
              })
            }
            selectedObject.parent.children.forEach((item) => {
              if (item.textureName !== 'SELECTED') {
                this.curTexture = item.textureName
                item.material.map = selected
                item.textureName = 'SELECTED'
                this.selectedPort = selectedObject
                this.$emit('portClick', selectedObject.parent.portData)
              } else {
                item.material.map = this.portStatus[this.curTexture].img
                item.textureName = this.curTexture
              }
            })
            this.faceGroup.forEach((item) => {
              const obj = item.find(
                (port) =>
                  port.portId === selectedObject.parent.portData.connPortId
              )
              if (obj) {
                obj.children.forEach((mesh) => {
                  if (mesh.textureName !== 'SELECTED') {
                    mesh.material.map = selected
                    mesh.textureName = 'SELECTED'
                  } else {
                    mesh.material.map = this.portStatus[this.curTexture].img
                    mesh.textureName = this.curTexture
                  }
                })
              }
            })
          }
          console.log(selectedObject)
        }
      },
      // 监听尺寸变化
      changeSize() {
        // 重置渲染器输出画布canvas尺寸
        this.renderer.setSize(
          this.$refs.draw.offsetWidth,
          this.$refs.draw.offsetHeight
        )
        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        // 重置相机投影的相关参数
        this.camera.aspect = k
        // 如果相机的一些属性发生了变化，
        // 需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
        this.camera.updateProjectionMatrix()
      },
      removeObj(obj) {
        let arr = obj.children.filter((x) => x)
        arr.forEach((item) => {
          if (item.children.length) {
            this.removeObj(item)
          } else {
            item.clear()
          }
        })
        obj.clear()
        arr = null
      }
    }
  }
</script>

<style lang="scss">
#three_div {
  width: 100%;
  height: 100%;
}

//进度条
.loadingBox {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: #020721;
  display: flex;
  align-items: center;
  justify-content: center;
}
.progress {
  height: 17px;
  background: #262626;
  padding: 3px;
  overflow: visible;
  border-radius: 20px;
  border-top: 1px solid #000;
  border-bottom: 1px solid #7992a8;
  margin-top: 50px;
  width: 300px;

  .progress-bar {
    border-radius: 20px;
    position: relative;
    animation: animate-positive 2s;
    float: left;
    width: 0;
    height: 100%;
    font-size: 12px;
    line-height: 20px;
    color: #fff;
    text-align: center;
    background-color: #2962f9;
    -webkit-transition: width 0.6s ease;
    -o-transition: width 0.6s ease;
    transition: width 0.6s ease;
  }

  .active {
    animation: reverse stripes 0.4s linear infinite, animate-positive 2s;
  }

  .progress-value {
    display: none;
    padding: 3px 7px;
    font-size: 13px;
    color: #fff;
    border-radius: 4px;
    background: #191919;
    border: 1px solid #000;
    position: absolute;
    top: -40px;
    right: -10px;
  }

  .progress-value:after {
    content: "";
    border-top: 10px solid #191919;
    border-left: 10px solid transparent;
    border-right: 10px solid transparent;
    position: absolute;
    bottom: -6px;
    left: 26%;
  }
}

.progress-bar-striped {
  background-image: -webkit-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.15) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255, 255, 255, 0.15) 50%,
    rgba(255, 255, 255, 0.15) 75%,
    transparent 75%,
    transparent
  );
  background-image: -o-linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.15) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255, 255, 255, 0.15) 50%,
    rgba(255, 255, 255, 0.15) 75%,
    transparent 75%,
    transparent
  );
  background-image: linear-gradient(
    45deg,
    rgba(255, 255, 255, 0.15) 25%,
    transparent 25%,
    transparent 50%,
    rgba(255, 255, 255, 0.15) 50%,
    rgba(255, 255, 255, 0.15) 75%,
    transparent 75%,
    transparent
  );
  -webkit-background-size: 40px 40px;
  background-size: 40px 40px;
}

@-webkit-keyframes stripes {
  from {
    background-position: 40px 0;
  }

  to {
    background-position: 0 0;
  }
}

@-o-keyframes stripes {
  from {
    background-position: 40px 0;
  }

  to {
    background-position: 0 0;
  }
}

@keyframes stripes {
  from {
    background-position: 40px 0;
  }

  to {
    background-position: 0 0;
  }
}

@-webkit-keyframes animate-positive {
  0% {
    width: 0;
  }
}

@keyframes animate-positive {
  0% {
    width: 0;
  }
}
</style>
